package pentago.engine;

import java.util.ArrayList;

import pentago.exceptions.PentagoInvalidPlatePositionException;
import pentago.exceptions.PentagoInvalidPositionException;
import pentago.exceptions.PentagoInvalidRotationException;

public class AlphaBetaAlgorithm implements IPentagoAlgorithm {

    private int minAlpha = Integer.MIN_VALUE + 1;
    private int maxBeta = Integer.MAX_VALUE;
    private int maxDepth;
    private long visitedNodesCount;
    private boolean timeout;

    public AlphaBetaAlgorithm(int depth) {
	this.maxDepth = depth;
    }

    @Override
    public Move getNextMove(byte[][] boardState, byte player, long maxRunTime) {

	GameState currentState = new GameState(boardState);
	GameState newState;
	ArrayList<Move> incompleteMovesList;
	ArrayList<Move> realMovesList = new ArrayList<Move>();
	ArrayList<GameState> realStatesList = new ArrayList<GameState>();
	Move nextMove = null;
	Move bestMove = null;
	Watchman watchman = new Watchman(maxRunTime, this);
	int alpha = minAlpha;
	int beta = maxBeta;
	int score;
	visitedNodesCount = 0;

	timeout = false;

	incompleteMovesList = currentState.generateIncompleteMoves();
	for (Move move : incompleteMovesList) {
	    newState = currentState.nextStateFromMove(move);
	    if (newState.isTerminal()) {
		score = -newState.evaluate();
		if (score >= GameState.FIVE_IN_ROW_SCORE / 2) {
		    return move;						// Found finishing move
		}
	    }

	    for (int platePosIndex = 0; platePosIndex < PentagoUtils.platePositions.length; ++platePosIndex) {
		for (int rotationIndex = 0; rotationIndex < PentagoUtils.rotations.length; ++rotationIndex) {
		    try {
			nextMove = new Move(move.getPosition(),
				PentagoUtils.platePositions[platePosIndex],
				PentagoUtils.rotations[rotationIndex]);
		    } catch (PentagoInvalidPositionException
			    | PentagoInvalidRotationException
			    | PentagoInvalidPlatePositionException e) {
			e.printStackTrace();
		    }

		    newState = currentState.nextStateFromMove(nextMove);
		    if (newState.isTerminal()) {
			score = -newState.evaluate();
			if (score >= GameState.FIVE_IN_ROW_SCORE / 2) {
			    return nextMove;						// Found finishing move
			}
			if (score > alpha) {
			    alpha = score;
			    bestMove = nextMove;
			}
		    } else {
			realMovesList.add(nextMove);
			realStatesList.add(newState);
		    }
		}
	    }
	}

	assert realMovesList.size() == realStatesList.size();

	PentagoUtils.randomizeMovesStates(realMovesList, realStatesList);

	watchman.start();

	for (int i = 0; i < realMovesList.size(); ++i) {
	    score = -alphaBeta(realStatesList.get(i), maxDepth, -beta, -alpha);
	    if (score >= GameState.FIVE_IN_ROW_SCORE / 2) {
		return realMovesList.get(i);						// Found good path
	    }
	    if (score > alpha) {
		alpha = score;
		bestMove = realMovesList.get(i);
	    }
	}

	watchman.shutdown();
	System.out
		.println("Visited nodes by standard Alpha-Beta for marble count of "
			+ currentState.getMarblesCount()
			+ ": "
			+ visitedNodesCount);

	return bestMove;
    }

    private int alphaBeta(GameState state, int depth, int alpha, int beta) {
	++visitedNodesCount;
	if (state.isTerminal() || depth == 0 || timeout)
	    return state.evaluate();

	int score;
	ArrayList<GameState> nextStates = PentagoUtils
		.generateNextStates(state);

	for (GameState nextState : nextStates) {
	    score = -alphaBeta(nextState, depth - 1, -beta, -alpha);
	    if (score > alpha) {
		alpha = score;
	    }
	    if (alpha >= beta)
		return beta;
	    if (timeout)
		break;
	}
	return alpha;
    }

    public void executeTimeout() {
	timeout = true;
    }
}
